#ifndef _HARDWARE_MODEL_H_
#define _HARDWARE_MODEL_H_

#include <cstdint>
#include <bitset>
#include "types.h"
#include "addrdec.h"

class Instruction;
class MemoryConfig;

const uint32_t MAX_MEMORY_ACCESS_SIZE = 128;
typedef std::bitset<MAX_MEMORY_ACCESS_SIZE> MemAccessByteMask;

const unsigned SECTOR_CHUNCK_SIZE = 4; // four sectors
const unsigned SECTOR_SIZE = 32;	   // sector is 32 bytes width
typedef std::bitset<SECTOR_CHUNCK_SIZE> MemAccessSectorMask;

enum MemAccessType
{
	GLOBAL_ACC_R = 0,
	LOCAL_ACC_R,
	GLOBAL_ACC_W,
	LOCAL_ACC_W,
	L1_WRBK_ACC,
	L2_WRBK_ACC,
	INST_ACC_R,
	L1_WR_ALLOC_R,
	L2_WR_ALLOC_R
};

enum MemFetchState
{
	MEM_FETCH_INITIALIZED = 0,
	IN_L1I_MISS_QUEUE,
	IN_L1D_MISS_QUEUE,
	IN_L1T_MISS_QUEUE,
	IN_L1C_MISS_QUEUE,
	IN_L1TLB_MISS_QUEUE,
	IN_VM_MANAGER_QUEUE,
	IN_ICNT_TO_MEM,
	IN_PARTITION_ROP_DELAY,
	IN_PARTITION_ICNT_TO_L2_QUEUE,
	IN_PARTITION_L2_TO_DRAM_QUEUE,
	IN_PARTITION_DRAM_LATENCY_QUEUE,
	IN_PARTITION_L2_MISS_QUEUE,
	IN_PARTITION_MC_INTERFACE_QUEUE,
	IN_PARTITION_MC_INPUT_QUEUE,
	IN_PARTITION_MC_BANK_ARB_QUEUE,
	IN_PARTITION_DRAM,
	IN_PARTITION_MC_RETURNQ,
	IN_PARTITION_DRAM_TO_L2_QUEUE,
	IN_PARTITION_L2_FILL_QUEUE,
	IN_PARTITION_L2_TO_ICNT_QUEUE,
	IN_ICNT_TO_SHADER,
	IN_CLUSTER_TO_SHADER_QUEUE,
	IN_SHADER_LDST_RESPONSE_FIFO,
	IN_SHADER_FETCHED,
	IN_SHADER_L1T_ROB,
	MEM_FETCH_DELETED,
	NUM_MEM_REQ_STA
};

enum MemFetchType
{
	READ_REQUEST = 0,
	WRITE_REQUEST,
	READ_REPLY, // send to shader
	WRITE_ACK
};

class MemFetch;

class MemAccess
{

public:
	MemAccess() {}
	MemAccess(MemAccessType type, addr_t addr, bool write, uint32_t req_size)
	{
		m_addr = addr;
		m_write = write;
		m_req_size = req_size;
		m_type = type;
	}
	MemAccess(MemAccessType type, addr_t addr, bool write, uint32_t req_size,
			  MemAccessByteMask byte_mask,
			  MemAccessSectorMask sector_mask)
	{
		m_addr = addr;
		m_write = write;
		m_req_size = req_size;
		m_type = type;
		m_byte_mask = byte_mask;
		m_sector_mask = sector_mask;
	}

	addr_t get_addr() const { return m_addr; }
	void set_addr(addr_t addr) { m_addr = addr; }
	uint32_t get_size() const { return m_req_size; }
	bool is_write() const { return m_write; }
	MemAccessType get_type() const { return m_type; }
	MemAccessByteMask get_byte_mask() const { return m_byte_mask; }
	MemAccessSectorMask get_sector_mask() const 
	{ 
		return m_sector_mask; 
	}

private:
	addr_t m_addr;
	bool m_write;
	uint32_t m_req_size;
	MemAccessType m_type;
	MemAccessByteMask m_byte_mask;
	MemAccessSectorMask m_sector_mask;
};

class MemFetchInterface
{
public:
	virtual bool full(unsigned size, bool write) const = 0;
	virtual void push(MemFetch *mf) = 0;
};

class MemFetchAllocator
{
public:
	virtual MemFetch *alloc(addr_t addr, MemAccessType type,
							uint32_t size, bool wr,
							uint64_t cycle) const = 0;
	virtual MemFetch *alloc(const MemAccess &access,
							uint64_t cycle) const = 0;
};

class MemFetch
{
public:
	MemFetch(const MemAccess &access, uint32_t ctrl_size, Instruction *ins,
			 uint32_t core_id, const MemoryConfig *config,
			 cycle_t cycle,
			 MemFetch *original_mf = NULL,
			 MemFetch *original_wr_mf = NULL);
	MemFetch(const MemAccess &access, uint32_t ctrl_size,
			 uint32_t core_id, const MemoryConfig *config,
			 cycle_t cycle,
			 MemFetch *original_mf = NULL,
			 MemFetch *original_wr_mf = NULL);
	~MemFetch();
	uint32_t m_mid;
	void set_mid(uint32_t mid) { m_mid = mid; }

	uint32_t get_data_size() const { return m_data_size; }
	void set_data_size(uint32_t size) { m_data_size = size; }
	uint32_t get_ctrl_size() const { return m_ctrl_size; }
	bool is_write() { return m_access.is_write(); }
	void set_addr(addr_t addr) { m_access.set_addr(addr); }
	addr_t get_addr() const { return m_access.get_addr(); }
	uint32_t get_access_size() const { return m_access.get_size(); }
	bool get_is_write() const { return m_access.is_write(); }
	uint32_t size() const { return m_data_size + m_ctrl_size; }
	Instruction* get_inst()const {return m_ins;}
	void set_inst(Instruction* ins){m_ins=ins;}
	const MemoryConfig *get_mem_config() const { return m_mem_config; }

	uint32_t get_request_id() const { return m_pe_id; }
	MemFetchType get_type() const { return m_type; }

	void set_return_timestamp(uint32_t t) { m_timestamp2 = t; }
	void set_icnt_receive_time(uint32_t t) { m_icnt_receive_time = t; }
	uint32_t get_timestamp() const { return m_timestamp; }
	uint32_t get_return_timestamp() const { return m_timestamp2; }
	uint32_t get_icnt_receive_time() const { return m_icnt_receive_time; }

	addr_t get_partition_addr() const { return m_partition_addr; }
	uint32_t get_sub_partition_id() const { return m_raw_addr.sub_partition; }

	uint32_t get_num_flits(bool simt_to_mem);
	void set_status(enum MemFetchState state, cycle_t time)
	{
		m_state = state;
		m_state_change_time = time;
	}

	MemFetch *get_original_mf() { return m_original_mf; }
	MemFetch *get_original_wr_mf() { return m_original_wr_mf; }

	MemAccessType get_access_type() const { return m_access.get_type(); }
	MemAccessByteMask get_access_byte_mask() const { return m_access.get_byte_mask(); }
	const addrdec_t &get_tlx_addr() const { return m_raw_addr; }
	void set_chip(uint32_t chip_id) { m_raw_addr.chip = chip_id; }
	void set_parition(uint32_t sub_partition_id)
	{
		m_raw_addr.sub_partition = sub_partition_id;
	}
	MemAccessSectorMask get_access_sector_mask() const
	{
		return m_access.get_sector_mask();
	}
	void set_reply()
	{
		assert(m_access.get_type() != L1_WRBK_ACC && m_access.get_type() != L2_WRBK_ACC);
		if (m_type == READ_REQUEST)
		{
			assert(!get_is_write());
			m_type = READ_REPLY;
		}
		else if (m_type == WRITE_REQUEST)
		{
			assert(get_is_write());
			m_type = WRITE_ACK;
		}
	}

private:
	uint32_t m_pe_id;
	MemAccess m_access;
	uint32_t m_data_size;
	uint32_t m_ctrl_size;

	addr_t m_partition_addr;
	addrdec_t m_raw_addr;

	cycle_t m_timestamp;
	cycle_t m_timestamp2;

	MemFetchType m_type;
	MemFetchState m_state;
	cycle_t m_state_change_time;
	cycle_t m_icnt_receive_time;

	uint32_t icnt_flit_size;

	MemFetch *m_original_mf;
	MemFetch *m_original_wr_mf;
	Instruction *m_ins;

	const MemoryConfig *m_mem_config;
};

#endif